package org.nvframe.manager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.nvframe.component.Component;
import org.nvframe.component.physics.state.PhysicsPosition;
import org.nvframe.entity.Entity;
import org.nvframe.event.EventListener;
import org.nvframe.event.EventService;
import org.nvframe.event.eventtype.UpdateEvent;
import org.nvframe.event.eventtype.UpdateListener;
import org.nvframe.exception.NVFrameRuntimeException;

/**
 * 
 * @author Nik Van Looy
 */
public class EntityManager implements UpdateListener {
	
	private static EntityManager _instance = new EntityManager();
	
	public static EntityManager getInstance() {
		return _instance;
	}

	private Map<Integer, Entity> entities;
	private Map<String, Integer> instanceRestriction;
	private List<Entity> deletionBuffer;
	
	private int nextInstanceId;
	
	private EntityManager() {
		// <EntityID, Entity>
		entities = new TreeMap<Integer,Entity> ();
		// <EntityInstanceID, MaxInstanceCount>
		instanceRestriction = new HashMap<String, Integer> ();
		
		deletionBuffer = new ArrayList<Entity> ();
		nextInstanceId = 0;
		
		EventService.getInstance().addEventListener(this);
	}
	
	/**
	 * Add an instanceRestriction
	 * 
	 * @param entityId The entity to apply the restriction to
	 * @param maxInstances The maximum amount of instances this entity may have
	 */
	public void addInstanceRestriction(String entityId, int maxInstances) {
		instanceRestriction.put(entityId, maxInstances);
	}
	
	/**
	 * Add an Entity to the manager for later reference
	 * 
	 * @param entity The entity to add
	 */
	public void addEntity(Entity entity) {
		String entityId = entity.getId();
		
		// instance limitation?
		if(instanceRestriction.containsKey(entityId)) {
			if(getInstancesCount(entityId, true) >= instanceRestriction.get(entityId)) {
				// remove first instance from this entity
				for(Entity entityR : entities.values()) {
					if(entityR.getId().equals(entityId) && entityR.isEnabled()) {
						entityR.removed();
						break;
					}
				}
			}
		}
		
		// create new instance id
		int instanceId = getNextId();
		
		// add to the entitiesMap
		entity.setInstanceId(instanceId);
		entities.put(instanceId, entity);
	}
	
	/**
	 * Get a collection of alle Entities in the Manager instance
	 * 
	 * @return The entity collection
	 */
	public Collection<Entity> getEntities() {
		return new ArrayList<Entity> (entities.values());
	}
	
	/**
	 * Get an Entity reference from a specified id
	 * 
	 * @param entityId The Entity's id
	 * @return The Entityy
	 */
	public Entity getEntity(String entityId) {
		
		for(Entity entity : entities.values())
			if(entity.getId().equals(entityId))
				return entity;
		
		throw new NVFrameRuntimeException("EntityManager: entity with id '" + entityId + "' not found");
	}
	
	/**
	 * Count all instances of an entitiy with a specified id
	 * 
	 * @param entityId The Entity's id
	 * @return int instance count
	 */
	public int getInstancesCount(String entityId) {
		int count = 0;
		
		for(Entity entity : entities.values())
			if(entity.getId().equals(entityId))
				count++;
		
		return count;
	}
	
	/**
	 * Count active or inactive instances of an entitiy with a specified id 
	 * 
	 * @param entityId The Entity's id
	 * @param enabled true counts only enabled, false otherwise
	 * @return int instance count
	 */
	public int getInstancesCount(String entityId, boolean enabled) {
		int count = 0;
		
		for(Entity entity : entities.values())
			if(entity.getId().equals(entityId) && entity.isEnabled() == enabled)
				count++;
		
		return count;
	}

	/**
	 * Get the next Entity instance id
	 * 
	 * @return The next Entity-instance id
	 */
	private int getNextId() {
		return ++nextInstanceId;
	}

	/**
	 * Shedule a given entity for deletion
	 * adds the entity to the deletionBuffer
	 * 
	 * @param entity The entity to delete from the Memory
	 */
	public void sheduleForDeletion(Entity entity) {
		deletionBuffer.add(entity);
	}
	
	/**
	 * removes all entity instances
	 */
	public void removeAllEntities() {
		// remove entity instances
		for(Entity entity : entities.values())
			deletionBuffer.add(entity);
		
		// remove restriction settings
		instanceRestriction.clear();
	}
	
	public void executeEntityDeletion() {
		// create a HashSet to remove duplicates
		HashSet<Entity> hashSet = new HashSet<Entity>(deletionBuffer);
		ArrayList<Entity> deletionBufferCpy = new ArrayList<Entity>(hashSet) ;
		
		deletionBuffer.clear();
		
		for(Entity entity : deletionBufferCpy) {
			/*if(ConfigManager.getInstance().getActive("debugMode"))
				System.out.println("removing entity from memory [id: " + entity.getId() +
						", instance: " + entity.getInstanceId() + "]");*/
			
			entity.setEnabled(false);
			
			// remove component from EventService when its a listener
			for(Component comp : entity.getComponents()) {
				if(comp instanceof EventListener)
					EventService.getInstance().removeEventListener((EventListener) comp);
				
				comp.removed();
			}
			
			// same for entity
			if(entity instanceof EventListener)
				EventService.getInstance().removeEventListener((EventListener) entity);
			
			// entity has physics => remove the body from the world
			if(entity.hasComponent(PhysicsPosition.class))
				CollisionManager.getInstance().sheduleBodyDelition(entity);
			
			//remove references from entity to its components
			entity.deleteAllComponents();
			
			// remove the entity from the entitymap
			if(entities.remove(entity.getInstanceId()) == null)
				throw new NVFrameRuntimeException("EntityManager: cannot delete entity instance from memory" +
						" [id: " + entity.getId() + ", instance: " + entity.getInstanceId() + "]");
		}
	}
	
	/**
	 * Print all Entities held by the Manager
	 */
	public void printEntities() {
		for(Entity entity : entities.values()) {
			if(instanceRestriction.containsKey(entity.getId()))
				System.out.println("entity => instance: " + entity.getInstanceId() + " id: " + entity.getId() + " maxinstances: " + 
						instanceRestriction.get(entity.getId()) + " current instances: " + getInstancesCount(entity.getId()));
			else
				System.out.println("entity => instance: " + entity.getInstanceId() + " id: " + entity.getId());
		}
	}
	
	@Override
	public void onUpdate(UpdateEvent event) {
		executeEntityDeletion();
	}

}
